/*
 * Copyright (C) 2011 Fabio Hufschmid at <hufsf1@bfh.ch>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ch.bfh.aeneas.core.io;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Vibrator;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import ch.bfh.aeneas.core.io.tts.bloomfilter.LangFinder;
import ch.bfh.aeneas.exchange.intent.AeneasIntent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;

/**The OutputService is intended to act as centralized authority responsible for giving the user a feedback
 *
 * @author Fabio Hufschmid at <hufsf1@bfh.ch>
 */
public class OutputService extends Service implements TextToSpeech.OnInitListener {

    /** constants **/
    private final static String TAG = "OutputService";
    private TextToSpeech mTts;
    private Vibrator mVibrator;
    private ToneGenerator mToneGenerator;
    private SpeechThread SThread;
    private AIOffSpeechThread mAIOffSThread;
    private VibrateThread VThread;
    private BeepThread BThread;
    private volatile boolean mIsBeeping;
    private volatile boolean mIsVibrating;
    private volatile boolean mIsSpeaking;
    private String mSpeechtext;
    private Locale mLoc;
    private LangFinder mLF;
    private int mQueueType;
    private int vibtime;

    /**
     * This inner class is used to receive commands for the OutpuService.
     */
    public class OutputSBroadcastReceiver extends BroadcastReceiver {

        private final static String TAG = "OutputSBroadcastReceiver";
        
        /**
         * With the received intent you have to set the functionality of this OutputService. 
         * 
         * @param context
         * @param intent    With the received intent you have to set the functionality of this OutputService. 
         *                  For text to speech you have to set the Key in extras "Speech" with a String value represent for the text which is to speak, 
         *                  to vibrate set the extra Key "Vib" with no value.
         *                  If you want change the spoken output language, send the intent with "SetLocale" in extras before you send the intent for a spoken
         *                  You can also set the Local over extras with the string "SetLocale" followed by "ger" for german, "eng" for english and "fr" for french. 
         */
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(TAG, "OnReceive...");

            Iterator<String> keyIter = intent.getExtras().keySet().iterator();
            String command;
            Bundle extras = intent.getExtras();
            
            while (keyIter.hasNext()) {
                command = keyIter.next();

                if (command.equalsIgnoreCase("speech")) {
                    setSpeechText(extras.getString("speech"));
                    
                    boolean useAi = intent.getBooleanExtra("useAi", false);
                    
                    if(useAi) {
                        startSpeechThread();
                    } else {
                        startAIOffSpeechThread(); 
                    }
                } else if (command.equalsIgnoreCase("vib")) {
                    Log.i(TAG, "vibriere...");
                    if(extras.containsKey("milliTime")) {
                        setVibTime(Integer.parseInt(extras.getString("milliTime")));         
                    }
                    startVibrateThread();
                } else if (command.equalsIgnoreCase("locale")) {
                    Log.i(TAG, "Set Locale...");
                    /*if (extras.getString("locale").equals("ger")) {
                        setLocale(Locale.GERMAN);
                    } else if (extras.getString("locale").equals("eng")) {
                        setLocale(Locale.ENGLISH);
                    } else if (extras.getString("locale").equals("fr")) {
                        setLocale(Locale.FRENCH);
                    }*/
                    setLocale((Locale)extras.get("locale"));
                }
            }
        }
    }
    
    private OutputSBroadcastReceiver oSBR = new OutputSBroadcastReceiver();

    public class OutputServiceBinder extends Binder {        
        public OutputService getService() {
            return OutputService.this;
        }              
    }
    private final OutputServiceBinder mBinder = new OutputServiceBinder();
    
    @Override
    public IBinder onBind(Intent arg0) {
        return mBinder;
    }

    /**
     * Called when the service is first created. 
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "on create...");
        
        getApplicationContext().registerReceiver(oSBR, new IntentFilter(AeneasIntent.ACTION_OUTPUT));

        mTts = new TextToSpeech(this, this);
        mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        mToneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);

        vibtime=10;
        setLocale(Locale.GERMAN);
        this.setQueueType("ADD");

        SThread = new SpeechThread();
        VThread = new VibrateThread();
        BThread = new BeepThread();
        mAIOffSThread=new AIOffSpeechThread();
        
        try {
            mLF = new LangFinder(this);
        } catch (IOException ex) {
            Log.e(TAG, "Error getting new LangFinder");
        }
    }

    @Override
    public void onDestroy() {
        if (mTts != null) {
            mTts.stop();
            mTts.shutdown();
            this.stopSpeechThread();
            this.stopAIOffSpeechThread();
        }
        if (mVibrator != null) {
            this.stopVibrateThread();
        }
        if (mToneGenerator != null) {
            this.stopBeepThread();
        }
        getApplicationContext().unregisterReceiver(oSBR);
    }


    public void onInit(int arg0) {
        mTts.setLanguage(mLoc);
    }

    /**
     * This methode change the behaviour of the TTS queue.
     * @param inS   ADD set the queue to TextToSpeech.QUEUE_ADD
     *              FLUSH set the queue to TextToSpeech.QUEUE_FLUSH
     */
    public void setQueueType(String inS) {
        if (inS.equals("ADD")) {
            this.mQueueType = TextToSpeech.QUEUE_ADD;
        } else if (inS.equals("FLUSH")) {
            this.mQueueType = TextToSpeech.QUEUE_FLUSH;
        }
    }

    /**
     * TTS with integrated intelligence to the correct language and pronunciation
     * intelligence because the tts change now for every language block to the correct android Locale and the correct pronunciation
     * There are three steps for this integrated intelligence
     * Step 1: Method splitSentenceToLang
     * Step 2: LangFinder.getLang
     * Step 3: Method intbuildLangBlocks
     * more about this methods see in particular javadoc
     * 
     */
    public class SpeechThread extends Thread {

        @Override
        public void run() {
            //Log.i(TAG,"Default Local is: "+Locale.getDefault());
            ArrayList<ArrayList> WordBlocksAL;

            //Attention: to deactivate in tts settings "use always my Settings" on the android device!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
            WordBlocksAL=splitSentenceToLang(getSpeechText());
            //Log.i(TAG,"ArrayList to speek is: "+WordBlocksAL.toString());                                
            for(int i=0;WordBlocksAL.size()>i;i++)
            {
                mLoc=findLocal(Integer.parseInt(((WordBlocksAL.get(i)).get(0)).toString()));
                //Log.i(TAG,"Locale is: "+mLoc.toString());
                String out="";

                for(int j=1;(WordBlocksAL.get(i)).size()>j;j++)
                {   
                    //Log.i(TAG,"In size: "+(WordBlocksAL.get(i)).size());
                    out=out.concat(((WordBlocksAL.get(i)).get(j)).toString());
                    //Log.i(TAG,"Text: "+((WordBlocksAL.get(i)).get(j)).toString());
                    out=out.concat(" ");
                }
                //Log.i(TAG,"Read string is: "+out);
                onInit(0);
                mTts.speak(out,mQueueType,null);
            }
        }
    }
    /**    TTS without integrated intelligence.
    *      Speak text in the language which the os is configured
    * 
    */
    public class AIOffSpeechThread extends Thread {

        @Override
        public void run() {  
            mLoc=Locale.getDefault();                              
            onInit(0);
            mTts.speak(getSpeechText(),mQueueType,null);
        }
    }

     /**
     * To run the vibration 
     */
    public class VibrateThread extends Thread {

        public VibrateThread() {
        }

        @Override
        public void run() {
            mIsVibrating = true;

            vibrate(vibtime);

            mIsVibrating = false;
        }
    }

     /**
     * To run the beep 
     */
    public class BeepThread extends Thread {

        public BeepThread() {
        }

        @Override
        public void run() {
            mIsBeeping = true;

            beep(ToneGenerator.TONE_PROP_BEEP, 200);

            mIsBeeping = false;
        }
    }

     /**
     * To start the AIOffSpeechThread
     */
    public synchronized void startAIOffSpeechThread() {
        mAIOffSThread.run();
    }
    
    /**
     * To stop the AIOffSpeechThread
     */
    public synchronized void stopAIOffSpeechThread() {
        mAIOffSThread.stop();
    }
    
    /**
     * To start the SpeechThread
     */
    public synchronized void startSpeechThread() {
        SThread.run();
    }
    
    /**
     * To stop the SpeechThread
     */
    public synchronized void stopSpeechThread() {
        SThread.stop();
    }

    /**
     * To start the VibrateThread
     */
    public synchronized void startVibrateThread() {
        VThread.run();
    }

    /**
     * To stop the VibrateThread
     */
    public synchronized void stopVibrateThread() {
        VThread.stop();
    }

    /**
     * To start the BeepThread
     */
    public synchronized void startBeepThread() {
        BThread.run();
    }

    /**
     * To stop the BeepThread
     */
    public synchronized void stopBeepThread() {
        BThread.stop();
    }

    /**
     * Register the Locale which is used for the spoken language
     * @param inLoc 
     */
    public void setLocale(Locale inLoc) {
        this.mLoc = inLoc;
    }

    /**
     * Register the text to speak out over tts
     * @param inText    text to speak
     */
    public void setSpeechText(String inText) {
        this.mSpeechtext = inText;
    }

    /**
     * Find out which text is set at moment
     * @return  the string wich is set at the moment for tts
     */
    public String getSpeechText() {
        return this.mSpeechtext;
    }

    /**
     * Register the time in milisecounds which is used if you start the vibrate thread
     * @param inTime     overwrite the default time
     */
    public void setVibTime(int time) {
        this.vibtime = time;
    }

     /**
     * Vibrate a given time.
     * 
     * @param time      how long that the vibrator have to vibrate (time in milliseconds)
     */
    public void vibrate(int time) {
        this.setVibTime(time);
        mVibrator.vibrate(time);
        Log.i(TAG, "In vibrate...");
    }

    /**
     * to stop the vibrator
     */
    public void stopVibrate() {
        mVibrator.cancel();
    }

    /**
     *This method starts the playback of a tone of the specified type for the specified duration.
     * 
     * Parameters
     * 
     * @param toneType      The type of tone generated @see startTone(int).
     * @param durationMs    The tone duration in milliseconds. If the tone is limited in time by definition, 
     *                      the actual duration will be the minimum of durationMs and the defined tone duration. 
     *                      Setting durationMs to -1, is equivalent to calling startTone(int).
     */
    public void beep(int toneType, int durationMs) {
        mToneGenerator.startTone(toneType, durationMs);
        Log.i(TAG, "In beep...");
    }
    
    /**
     * This mehtode is step one for a better tts output if there words with different languages in one sentence
     * 
     * This mehtode split an input string in a integer array. 
     * Every integer on position in the array represent the language of a word  
     * @param inSen     A string, which most represent sentences, which can contains word in different languages
     * @return          A two dimension arraylist. In dimension one are the blocks of strings with the same language
     *                  In dimension dwo are the code for the language followed by the words
     */
    public ArrayList<ArrayList> splitSentenceToLang(String inSen)
    {
        //Log.i(TAG,"in methode splitSentence");
        String[] words = inSen.split(" ");
        int[] lang = new int[words.length];
        ArrayList<ArrayList> LangBlocksAL=new ArrayList();
        
        for(int i=0;words.length>i;i++)
        {
            //set a interger specified for a found language. Step two is mthode call mLF.getLang(words[i])
            lang[i]=getLangCode(mLF.getLang(words[i]));
            Log.i(TAG,words[i]+" mit Index: "+i+" is: "+lang[i]);
        }
        
        //words to LangBlocks
        LangBlocksAL=intbuildLangBlocks(lang,words);     
        return LangBlocksAL;
    }
    
    /**This mehtode is step three for a better tts output if there words with different languages in one sentence
     * 
     * (0=english;1=french;2=german)
     * e.g. value from variable textA is [Hallo,ich,bin,la,général,the,grosse]
     * e.g. value from variable sntPart is [2,2,2,1,1,0,2] 
     * 1. take the langcode [2,2,2,1,1,0,2] and set this in the new arrayList blockArray: [2]
     *                       ^
     * 2. take the first word from StringArray textA an add it to blockArray: [2,Hallo]
     * 3. go to the next element to check if it the same langcode, if yes then add the word to blockArray
     *    if the languagecode changes add the current blockArray to the outArry and go out of the for loop and beginns with a new blockArray: [[2,Hallo,ich,bin]]
     * 4. At the end there is the following outArray: [[2,Hallo,ich,bin],[1,la,général],[0,the],[2,grosse]]
     * 
     * 
     * @param sntPart   the languagecode representation of every word in the param textA
     * @param textA     the sentence with the real words
     * @return          the two dimensionnel ArrayList with the sentences in same language
     */
    public ArrayList<ArrayList> intbuildLangBlocks(int[] sntPart, String[] textA)
    {
        ArrayList<String> blockArray;
        ArrayList<ArrayList> outArray=new ArrayList();
        boolean Notend=true;
        int z =0;
        
        for(int i = 0; i < sntPart.length; i++)
        {
            if(Notend)
            {
                Log.i(TAG,"Sprache: "+sntPart[i]);
                 blockArray = new ArrayList();
                 blockArray.add(Integer.toString(sntPart[i]));
                for(int j = i; j < sntPart.length; j++)
                {
                    if(sntPart[i] == sntPart[j])
                    {
                        Log.i(TAG,"Index gehört dazu: "+j);
                        blockArray.add(textA[j]);
                        
                        //find last element
                        if(j==(sntPart.length-1))
                        {
                            outArray.add(blockArray);
                            Log.i(TAG,"if "+blockArray.toString());
                            Notend=false;
                            break;
                        }
                    }
                    else
                    {
                         i = j-1;
                         Log.i(TAG,"else "+blockArray.toString());
                         outArray.add(blockArray);
                         z++;
                        break;
                    }
                }
            }
        }
        Log.i(TAG,outArray.toString());
        return outArray;
    }
     
    /**The translation beetween the langcode to the android Locale for tts output
     * 
     * @param gld   internal languagecode
     * @return      the android Locale for the tts output
     */
    private Locale findLocal(int gld)
    {
        //default language, in case that the word is not in the bloomfilter,
        //is the language of the android system
        Locale l=Locale.getDefault();
        if(gld==0)
        {
             l=Locale.US;
        }
        else if(gld==1)
        {
            l=Locale.FRENCH;
        }
        else if(gld==2)
        {
            l=Locale.GERMAN;
        }
        
        return l;

    }
    
    /**The translation beetween languageshortcut from LangFinder to the intrnal languagecode for intelligent language analysis
     * 
     * @param inStr     languageshortcut used by LangFinder
     * @return          the intrnal languagecode for intelligent language analysis
     */
    public int getLangCode(String inStr) 
    {
        //Log.i(TAG,"getLangCode "+inStr);
        if(inStr.equals("en"))
        {
            return 0;
        }
        else if(inStr.equals("fr"))
        {
            return 1;
        }
        else if(inStr.equals("de"))
        {
            return 2;
        }
        else
        {
            return -1;
        }
    } 
}